#include <ma.h>
#include <mastdlib.h>
#include <conprint.h>

#include <MoBox2D/Box2D.h>

#include "MAHeaders.h"

int gScreenWidth;
int gScreenHeight;

b2World *gWorld;
b2Body *gWall;
b2Body *gBody;
b2Fixture *gPaddleFixture;

#define SCALE 1.0

#define PADDLE_WIDTH	120
#define PADDLE_HEIGHT	40

#define MAX_BALLS 3
#define BALL_WIDTH 35
#define RGBA(r, g, b, a) ((r)<<16)|((g)<<8)|((b))|((a)<<24)

// Ahhh!, nothing like dangerous, multi-valued stuff...  But it's handy!
#define SCREEN_TO_PHYS(x, y) (x)/SCALE, (y)/SCALE
#define PHYS_TO_SCREEN(x, y) (x)*SCALE, (y)*SCALE

class ContactListener : public b2ContactListener {
public:
	/// Called when two fixtures begin to touch.
	void BeginContact(b2Contact* contact) {
	}
};

class QueryCallback : public b2QueryCallback {
public:
	bool ReportFixture(b2Fixture* fixture) {
	}
};

class CCSprite {
public:
	CCSprite(unsigned int *data, int width, int height) {
		this->data = data;
		this->width = width;
		this->height = height;
	}

	int getWidth() {
		return this->width;
	}
	int getHeight() {
		return this->height;
	}
	unsigned int *getData() {
		return this->data;
	}

private:
	unsigned int *data;
	int width;
	int height;
};

#define PI 3.14159265

double degreeToRadian2(double degree) {
	double radian = 0;
	radian = degree * (PI/180);
	return radian;
}

void drawLine(int x1, int y1, int x2, int y2, int origx, int origy, int angle)
{
	int newx1 = origx + ( cos(degreeToRadian2(angle)) * (x1 - origx) - sin(degreeToRadian2(angle)) * (y1 - origy) );
	int newy1 = origx + ( sin(degreeToRadian2(angle)) * (x1 - origx) + cos(degreeToRadian2(angle)) * (y1 - origy) );
	int newx2 = origx + ( cos(degreeToRadian2(angle)) * (x2 - origx) - sin(degreeToRadian2(angle)) * (y2 - origy) );
	int newy2 = origx + ( sin(degreeToRadian2(angle)) * (x2 - origx) + cos(degreeToRadian2(angle)) * (y2 - origy) );

//	printf("newx1=%d, newy1=%d, newx2=%d, newy2=%d\n", newx1, newy1, newx2, newy2);

	maLine(newx1, newy1, newx2, newy2);
}

void drawRect(int left, int top, int width, int height, int angle)
{
	int origx = left+width/2;
	int origy = top+height/2;
	int x2 = left+width;
	int y2 = top+height;

	drawLine(left, top, x2, top, origx, origy, angle);	// TOP
	drawLine(x2, top, x2, y2, origx, origy, angle);	// RIGHT
	drawLine(left, y2, x2, y2, origx, origy, angle);	// BOTTOM
	drawLine(left, top, left, y2, origx, origy, angle);	// LEFT
}

void init() {
	srand(maLocalTime());

	// Create a world
	b2Vec2 gravity = b2Vec2(0.0f, 30.0f);
	bool doSleep = true;
	gWorld = new b2World(gravity, doSleep);

	// Create edges around the entire screen
	b2BodyDef groundBodyDef;
	b2Body *groundBody = gWorld->CreateBody(&groundBodyDef);
	b2PolygonShape groundBox;
	b2FixtureDef boxShapeDef;
	boxShapeDef.shape = &groundBox;
	groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(SCREEN_TO_PHYS(gScreenWidth, 0)));
	groundBody->CreateFixture(&boxShapeDef);
	groundBox.SetAsEdge(b2Vec2(0,0), b2Vec2(SCREEN_TO_PHYS(0, gScreenHeight)));
	groundBody->CreateFixture(&boxShapeDef);
	groundBox.SetAsEdge(b2Vec2(SCREEN_TO_PHYS(0, gScreenHeight)), b2Vec2(SCREEN_TO_PHYS(gScreenWidth, gScreenHeight)));
	groundBody->CreateFixture(&boxShapeDef);
	groundBox.SetAsEdge(b2Vec2(SCREEN_TO_PHYS(gScreenWidth, gScreenHeight)), b2Vec2(SCREEN_TO_PHYS(gScreenWidth, 0)));
	groundBody->CreateFixture(&boxShapeDef);

	// Create paddle body
	b2BodyDef paddleBodyDef;
//	paddleBodyDef.type = b2_dynamicBody;
	paddleBodyDef.position.Set(SCREEN_TO_PHYS(gScreenWidth/2, gScreenHeight-100));
	unsigned int *paddleText = new unsigned int [PADDLE_WIDTH*PADDLE_HEIGHT];
	MARect srcRect = {0, 0, PADDLE_WIDTH, PADDLE_HEIGHT};
	maGetImageData(R_PADDLE, paddleText, &srcRect, PADDLE_WIDTH);
	CCSprite *sprite = new CCSprite(paddleText, PADDLE_WIDTH, PADDLE_HEIGHT);
	paddleBodyDef.userData = sprite;
	b2Body *paddleBody = gWorld->CreateBody(&paddleBodyDef);

	// Create paddle shape
	b2PolygonShape paddleShape;
	paddleShape.SetAsBox(SCREEN_TO_PHYS(60, 20));

	// Create shape definition and add to body
	b2FixtureDef paddleShapeDef;
	paddleShapeDef.shape = &paddleShape;
	paddleShapeDef.density = 1.0f;
	paddleShapeDef.friction = 0.0f;
	paddleShapeDef.restitution = 0.0f;
	gPaddleFixture = paddleBody->CreateFixture(&paddleShapeDef);

	srand(maLocalTime());
	for(int i = 0; i < MAX_BALLS; i++) {
		// Create ball body and shape
		b2BodyDef ballBodyDef;
		ballBodyDef.type = b2_dynamicBody;
		int spawnWidth = (gScreenWidth-100);
		ballBodyDef.position.Set(spawnWidth*(rand()/(RAND_MAX+1.0))+50, (gScreenHeight/2)*(rand()/(RAND_MAX+1.0)));

		unsigned int *ballText = new unsigned int [BALL_WIDTH*BALL_WIDTH];
		MARect srcRect = {0, 0, BALL_WIDTH, BALL_WIDTH};
		int textId = R_BALL1;
		if(i == 1) textId = R_BALL2;
		if(i == 2) textId = R_BALL3;
		maGetImageData(textId, ballText, &srcRect, BALL_WIDTH);
		CCSprite *sprite = new CCSprite(ballText, BALL_WIDTH, BALL_WIDTH);
		ballBodyDef.userData = sprite;

		b2CircleShape circle;
		circle.m_radius = (BALL_WIDTH/2)/SCALE;

		b2FixtureDef ballShapeDef;
		ballShapeDef.shape = &circle;
		ballShapeDef.density = 1.0f;
		ballShapeDef.friction = 0.4f;
		ballShapeDef.restitution = 0.8f;

		gBody = gWorld->CreateBody(&ballBodyDef);
		gBody->CreateFixture(&ballShapeDef);
	}

	for(int i = 0; i < MAX_BALLS; i++) {
		b2BodyDef boxBodyDef;
		boxBodyDef.type = b2_dynamicBody;
		int spawnWidth = (gScreenWidth-100);
		boxBodyDef.position.Set(spawnWidth*(rand()/(RAND_MAX+1.0))+50, (gScreenHeight/2)*(rand()/(RAND_MAX+1.0)));
		b2Body *boxBody = gWorld->CreateBody(&boxBodyDef);

		// Create box shape
		b2PolygonShape boxShape;
		boxShape.SetAsBox(SCREEN_TO_PHYS(20, 20));

		// Create shape definition and add to body
		b2FixtureDef boxShapeDef;
		boxShapeDef.shape = &boxShape;
		boxShapeDef.density = 1.0f;
		boxShapeDef.friction = 0.2f;
		boxShapeDef.restitution = 0.2f;
		boxBody->CreateFixture(&boxShapeDef);
	}
}

extern "C" int MAMain() {
	/// Get screen dimensions.
	MAExtent size = maGetScrSize();
	/// Extract the screen width
	gScreenWidth = EXTENT_X(size);
	/// Extract the screen height
	gScreenHeight = EXTENT_Y(size);

	init();

    // Now sim code
    float32 timeStep = 1.0f / 60.0f;
    int32 iterations = 1;

    float32 dt = timeStep;
    bool quit = false;
    while(!quit) {
		dt += timeStep;

		maSetColor(0x0);
		maFillRect(0, 0, gScreenWidth, gScreenHeight);

		int i = 0;
		gWorld->Step(timeStep, 1, iterations);
		for(b2Body *b = gWorld->GetBodyList(); b; b=b->GetNext())
		{
			i++;
			if (b->GetUserData() != NULL)
			{
				CCSprite *sprite = (CCSprite *)b->GetUserData();
				MAPoint2d *point = new MAPoint2d();
				point->x = (int)(b->GetPosition().x*SCALE - sprite->getWidth()/2);
				point->y = (int)(b->GetPosition().y*SCALE - sprite->getHeight()/2);
				MARect *rect = new MARect();
				rect->left=0;
				rect->top=0;
				rect->width=sprite->getWidth();
				rect->height=sprite->getHeight();
				float32 angle = b->GetAngle();
				maDrawRGB(point, sprite->getData(), rect, sprite->getWidth());

				delete point;
				delete rect;
			}
			else
			{
				maSetColor(0xffffff);
				drawRect(b->GetPosition().x*SCALE, b->GetPosition().y*SCALE, 20, 20, b->GetAngle());
			}
		}

		/// Updates the screen
		maUpdateScreen();

		/// Keep the backlight alive.
		maResetBacklight();

		MAEvent event;

		while(maGetEvent(&event)) {
			switch(event.type) {
			// Make sure we catch the close event and if we do, exit.
			case EVENT_TYPE_KEY_PRESSED:
				if(event.key >= MAK_FIRE && event.key <= MAK_SOFTRIGHT) {
					quit = true;
				}
				break;
			case EVENT_TYPE_CLOSE:
				quit = true;
			break;
			// Whenever the screen area or orientation of the screen changes, we reinitialize the FrameBuffer library.
			case EVENT_TYPE_SCREEN_CHANGED:
			break;
			}
		}
    }

    delete gWorld;
    return 0;
}
